﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
using System.Text.RegularExpressions;
using mshtml;
using System.Threading;
using System.Diagnostics;
using System.Xml;
using System.Xml.Schema;
using System.Runtime.Serialization;
using System.Collections.Concurrent;
using System.Data;
using SHDocVw;
using System.Windows.Forms;
namespace AutoPageHelper
{
    public delegate bool SubmitHandler(object sender, EventArgs e);
    
    [Serializable]
    [XmlRootAttribute(Namespace = "http://www.sdu.edu.cn/Boson")]
    public class PageManager
    {
        [NonSerialized]
        public static string strIEApp = "iexplore";
        public String UrlMain
        {
            get;
            set;
        }
        [NonSerialized]
        private int sleepmms = 100;
        public int SleepMilliseconds
        {
            get { return sleepmms; }
            set { sleepmms = value; }
        }
        [XmlArrayItem("Page")]
        public List<ActionPage> Pages;
        [NonSerialized]
        private ActionPage startPage;
        [XmlIgnore]
        public ActionPage StartPage
        {
            get{return startPage;}
        }
        [NonSerialized]
        ConcurrentQueue<ActionPage> delayedPages;
        [NonSerialized]
        private Thread delayThread;
        [NonSerialized]
        private IEProxy ie;
        [NonSerialized]
        private ConcurrentQueue<ActionPage> pageQueue;
        

        [NonSerialized]
        private Thread submitThread;
        [NonSerialized]
        private bool bCheck;
        [NonSerialized]
        private AutoResetEvent threadEvent;

        public event SubmitHandler OnSubmit;
        public event SubmitHandler OnFailed;
        public event SubmitHandler OnIEQuit;

        [NonSerialized]
        private IDataSource dataSource;
        [XmlIgnore]
        public IDataSource DataSource
        {
            get { return dataSource; }
            set { dataSource = value; }
        }


        [XmlIgnore]
        protected IEProxy IE
        {
            get 
            { 
                return ie; 
            }
            set
            {
                if (ie != null)
                    ie.Dispose();

                ie = value;
                if (ie != null)
                {
                    ie.OnBeforeNavigate += new IEEventHander(BeforeNavigate);
                    ie.OnClosed += new IEEventHander(IEQuit);
                    ie.OnDocumentComplete += new IEEventHander(DocComplete);
                }
            }
        }
        private void BeforeNavigate(IHTMLDocument doc, string url)
        {
            Cancel(doc);
        }
        private void DocComplete(IHTMLDocument doc, string url)
        {
            if (!bCheck)
                return;
            bool bOK = Push(doc);
            if (bOK)
                Debug.WriteLine("Push OK:" + url);
            else
                Debug.WriteLine("Push Error:" + url);
        }

        private void IEQuit(IHTMLDocument doc, string url)
        {
            if (OnIEQuit != null)
                OnIEQuit(this, null);
        }
        public bool BindWindow()
        {
            if (IE == null)
                return false;
            if (string.IsNullOrEmpty(UrlMain))
                UrlMain = Pages[0].Url;
            IE.CaptureNewWindow = true;
            return IE.Bind(UrlMain);
        }

        [XmlIgnore]
        public bool IsRunning
        {
            get { return bCheck && (submitThread != null) && (submitThread.IsAlive); }
        }

        [XmlIgnore]
        public bool SavePages
        {
            get { return (IE != null && IE.Record); }
            set { if(IE != null ) IE.Record = value;}
        }

        [XmlIgnore]
        public bool PageByPage
        {
            get;
            set;
        }
        [XmlIgnore]
        public List<String> Columns
        {
            get
            {
                List<String> cols = new List<String>();
                foreach (ActionPage pa in Pages)
                {
                    foreach (string col in pa.MappingKeys)
                    {
                        if (!cols.Contains(col))
                            cols.Add(col);
                    }
                }

                return cols;
            }
        }

        public PageManager()
        {
            Pages = new List<ActionPage>();
        }

        private void OnDeserialized(StreamingContext context)
        {
            // fill ref page
            foreach (ActionPage pa in Pages)
            {
                if (pa.Action.StartsWith("ref:"))
                {
                    string refUrl = pa.Action.Substring(4);
                    ActionPage rp = FindPage(refUrl);
                    pa.RefPage = rp;
                }
                if (pa.Type == PageType.Start)
                {
                    startPage = pa;
                }
            }
        }

        public void AddPage(ActionPage page)
        {
            Pages.Add(page);
        }

        public ActionPage this[string url]
        {
            get { return FindPage(url); }
        }

        public ActionPage this[string url, string type]
        {
            get { return FindPage(url, type); }
        }

        public ActionPage FindPage(string url)
        {
            if (String.IsNullOrEmpty(url))
                return null;
            return Pages.Find(p => p.RegUrl.IsMatch(url));
        }


        public ActionPage FindPage(string url, string type)
        {
            if (String.IsNullOrEmpty(url))
                return null;
            if (String.IsNullOrEmpty(type))
                return FindPage(url);
            return Pages.Find(p => p.RegUrl.IsMatch(url) && p.Type.Equals(type));
        }

        public ActionPage GetPageById(string id)
        {
            if (String.IsNullOrEmpty(id))
                return null;
            return Pages.Find(p => p.Id.Equals(id));
        }

        public void Save(string fileName)
        {
            FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
            XmlSerializer serializer = new XmlSerializer(this.GetType());
            serializer.Serialize(fs, this);
            fs.Close();
        }

        public bool Open()
        {
            if (!ActionPage.IsRegexUrl(UrlMain))
            {
                Process p = Process.Start(strIEApp, UrlMain + " -nomerge");
                return true;
            }
            return false;
        }
        public void End()
        {
            if (IE != null)
                IE = null;
        }

        /// <summary>
        /// 根据文件创建Manager
        /// </summary>
        public static PageManager Create(string fileName)
        {
            return Create(fileName, true);
        }
        public static PageManager Create(string fileName, bool validateScheam)
        {
            if (!File.Exists(fileName))
                return null;
            if (validateScheam)
            {
                if (!ValidateXml(fileName, Path.GetDirectoryName(fileName) + "\\" + typeof(PageManager).Name + ".xsd"))
                    return null;
            }
            FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            XmlSerializer serializer = new XmlSerializer(typeof(PageManager));
            PageManager pm = serializer.Deserialize(fs) as PageManager;
            fs.Close();
            if (pm != null)
            {
                BuildPageRelations(pm);
                pm.IE = new IEProxy();
                
            }
            return pm;
        }

        /// <summary>
        /// 建立页之间的关系
        /// </summary>
        private static void BuildPageRelations(PageManager pm)
        {
            Debug.Assert(pm != null);
            // fill refpage and prePages
            foreach (ActionPage pa in pm.Pages)
            {
                // refpage
                if (!String.IsNullOrEmpty(pa.Action))
                {
                    if (pa.Action.StartsWith("ref:"))
                    {
                        string refPa = pa.Action.Substring(4);
                        ActionPage rp = pm.GetPageById(refPa);
                        if (rp == null)
                            rp = pm.FindPage(refPa);
                        pa.RefPage = rp;
                    }
                }
                // prePages
                pa.SetPrePages(pm.Pages);

            }
        }

        public static bool ValidateXml(string xmlFile, string schemaFile)
        {
            bool bOK = true;
            try
            {
                XmlReaderSettings xst = new XmlReaderSettings();
                xst.ValidationType = ValidationType.Schema;
                xst.Schemas.Add(null, schemaFile);
                xst.ValidationEventHandler += new System.Xml.Schema.ValidationEventHandler(
                    delegate(object sender, ValidationEventArgs e)
                    {
                        bOK = false;
                    });
                XmlReader xr = XmlReader.Create(xmlFile, xst);
                while (xr.Read())
                {
                    ;
                }
            }
            catch (System.Exception)
            {
                bOK = false;
            }
            return bOK;
        }

        /// <summary>
        /// 提交当前页
        /// </summary>
        public bool Push(IHTMLDocument doc)
        {
            string url = (doc as IHTMLDocument2).url;
            if (string.IsNullOrEmpty(url))
                return false;
            if (!IsRunning)
                return false;
            ActionPage pa = this[url];
            if (pa == null)
                return false;

            // 查重，关于线程同步：如果此时没有，在遍历之后队列中肯定也没有
            // 因为在处理线程中，成功后才会出队
            ActionPage[] paa = pageQueue.ToArray();
            bool bFound = false;
            
            foreach (ActionPage p in paa)
            {
                if (pa.Equals(p))
                {
                    bFound = true;
                    break;
                }
            }

            pa.Document = doc;
            if (!bFound && pa.CanTriggered) // 不在队列里
            {
                pageQueue.Enqueue(pa);
            }

            threadEvent.Set();
            return true;

        }

        /// <summary>
        /// 取消当前文档的相关操作
        /// </summary>
        public bool Cancel(IHTMLDocument doc)
        {
            string url = (doc as IHTMLDocument2).url;
            return Cancel(url);
        }
        public bool Cancel(string url)
        {
            if (string.IsNullOrEmpty(url))
                return false;
            //if (!IsRunning)
            //    return false;
            ActionPage pa = this[url];
            if (pa == null)
                return false;
            if (pa.State != PageState.Finished)
            {
                pa.State = PageState.Canceled;
                return true;
            }
            return false;
        }

        /// <summary>
        /// 提交指定的Url
        /// </summary>
        public bool Act(string url)
        {
            ActionPage pa = FindPage(url);
            return Act(pa);
        }

        /// <summary>
        /// 提交指定的页
        /// </summary>
        public bool Act(ActionPage pa)
        {
            Debug.Assert(pa != null && dataSource != null);
            object data = dataSource.GetData();
            if (data != null)
            {
                if (data is string)
                    pa.SetData(data as string);
                else if (data is Dictionary<string, string>)
                    pa.SetData(data as Dictionary<string, string>);
                else
                    Debug.Fail("Not support data type.");
            }
           
            return pa.Submit();

        }

        public bool Restart()
        {
            if (startPage == null)
                startPage = Pages.Find(p => p.Type == PageType.Start);

            if (startPage != null)
            {
                startPage.Init();
                if (startPage.Document == null && IE != null && IE.MainWindow != null)
                    startPage.Document = IE.MainWindow.Document as IHTMLDocument;
                pageQueue.Enqueue(startPage);
                threadEvent.Set();
                return true;
            }
            else
            {
                return ReloadPage();
            }
        }

        public bool ReloadPage()
        {
            if (IE != null && IE.MainWindow != null) // reload for documentcomplete
            {
                if (!ActionPage.IsRegexUrl(UrlMain))
                    IE.MainWindow.Navigate(UrlMain);
                else
                    IE.MainWindow.Navigate(IE.MainWindow.LocationURL);
                return true;
            }
            return false;
        }

        /// <summary>
        /// 开始
        /// </summary>
        public void Start()
        {
            Debug.WriteLine("Start.");
            if (bCheck)
            {
                Stop();
            }
            
            if (threadEvent == null)
                threadEvent = new AutoResetEvent(false);

            if (submitThread==null || !submitThread.IsAlive)
            {
                submitThread = new Thread(new ThreadStart(SubmitThreadProc));
                submitThread.IsBackground = true;
                submitThread.SetApartmentState(ApartmentState.STA); // for IHTMLWindow
                submitThread.Priority = ThreadPriority.AboveNormal;
                if (pageQueue == null)
                    pageQueue = new ConcurrentQueue<ActionPage>(); //Queue.Synchronized(new Queue(Pages.Count));
                //urlQueue.Clear()
                while (!pageQueue.IsEmpty)
                {
                    ActionPage result;
                    pageQueue.TryDequeue(out result);
                }
            }
            bCheck = true;
            submitThread.Start();
            //submitThread.Join();
            Thread.Sleep(200); // 等待线程启动
            Restart();
        }

        /// <summary>
        /// 停止
        /// </summary>
        public void Stop()
        {
            bCheck = false;
            if (submitThread == null)
                return;
            if (submitThread.IsAlive)
            {
                threadEvent.Set();
            }
            Debug.WriteLine("Stop.");
        }


        /// <summary>
        /// 检查是否最后的页
        /// </summary>
        private bool CheckEnd(ActionPage pa)
        {
            Debug.Assert(pa != null);
            bool bContinue = true;
            if (pa.Type == PageType.End
                || pa.State == PageState.End) // 最后一页或提前结束
            {
                // 流程结束，恢复参数，准备下一轮
                foreach (ActionPage p in Pages)
                {
                    p.Init();
                    p.Counter = 0;
                }
                if(dataSource != null)
                    bContinue = dataSource.MoveNext() && bContinue;
                if (OnSubmit != null)
                    bContinue = OnSubmit(pa, null) && bContinue;// 外部事件返回

                if (bContinue)
                {
                    Restart();
                }
            }
            return bContinue;
        }

        /// <summary>
        /// 检查处理失败的页
        /// </summary>
        private bool CheckFailed(ActionPage pa)
        {
            Debug.Assert(pa != null);
            bool bContinue = true;

            if (OnFailed != null)
                bContinue = OnFailed(pa, null);
            if (bContinue && startPage != null)
            {
                startPage.Init(); // refresh 
                pageQueue.Enqueue(startPage);
            }

            return bContinue;
        }

        /// <summary>
        /// 处理被延迟的页
        /// </summary>
        private void CheckDelay(ActionPage pa)
        {
            Debug.Assert(pa != null);
            if (delayThread == null || !delayThread.IsAlive)
            {
                delayThread = new Thread(new ThreadStart(DelaySubmitThreadProc));
                delayThread.IsBackground = true;
                delayThread.SetApartmentState(ApartmentState.STA); // for IHTMLWindow
            }
            if (delayedPages == null)
                delayedPages = new ConcurrentQueue<ActionPage>();
            delayedPages.Enqueue(pa);
            delayThread.Start();
        }

        private void DelaySubmitThreadProc()
        {
            while (!delayedPages.IsEmpty && bCheck)
            {
                ActionPage pa;
                if (!delayedPages.TryPeek(out pa))
                    continue;
                bool bAct = pa.DoAction();
                delayedPages.TryDequeue(out pa);
                if (bAct)
                {
                    switch (pa.State)
                    {
                        case PageState.Finished:
                            bCheck = CheckEnd(pa) && bCheck; // 如果是最后一页，同时结束提交进程
                            continue;
                            //break;
                        case PageState.Delayed:
                            delayedPages.Enqueue(pa);
                            Thread.Sleep(SleepMilliseconds);
                            break;
                        case PageState.Timeout:
                            pageQueue.Enqueue(pa); // 在主线程中抛出
                            break;
                    }
                } 
                else  // 异常情况，提交失败
                {
                    pageQueue.Enqueue(pa); // 交给主线程
                }
            }
        }

        private void SubmitThreadProc()
        {
            while (bCheck)
            {
                threadEvent.WaitOne();
                while (bCheck && pageQueue.Count > 0 )
                {
                    // 在入队时已去掉可能的重复值
                    ActionPage pa;
                    
                    if (!pageQueue.TryPeek(out pa)
                        || pa == null)
                        continue;

                    Debug.WriteLine("Try to act : " + pa.Url );
                    if (Act(pa))
                    {
                        Debug.WriteLine("Act OK: " + pa.Url + " with data: " + pa.DataString + " state: " + pa.State.ToString());
                        pageQueue.TryDequeue(out pa); // 删除
                        switch (pa.State)
                        {
                            case PageState.Finished: // 完成
                            case PageState.End:
                                bCheck = CheckEnd(pa) && bCheck;
                                break;
                            case PageState.Delayed: // 延迟
                                CheckDelay(pa);//交给另一线程DelaySubmitThread去处理
                                break;
                            case PageState.Timeout: // 超过尝试次数
                                if (!pa.AllowFailed) // 不允许失败，交用户确认
                                    bCheck = CheckFailed(pa) && bCheck;
                                break;
                            case PageState.Canceled: // 页面已重新加载
                                break;
                            default:
                                break;
                        }
                    }
                    else // 失败，再试一次
                    {
                        Debug.WriteLine("Act Failed: " + pa.Url + " with: " + pa.State.ToString());
                        Thread.Sleep(SleepMilliseconds); // 等待失败的页面响应
                    }
                } // while dequeue
            } // while bCheck
        } // Dosumbit
    } // class
}
